<?php
/**
 * Implements the OpenID attribute exchange specification, version 1.0
 * as of svn revision 370 from openid.net svn.
 *
 * @package OpenID
 */

/**
 * Require utility classes and functions for the consumer.
 */
require_once "Auth/OpenID/Extension.php";
require_once "Auth/OpenID/Message.php";
require_once "Auth/OpenID/TrustRoot.php";

define('Auth_OpenID_AX_NS_URI', 'http://openid.net/srv/ax/1.0');

// Use this as the 'count' value for an attribute in a FetchRequest to
// ask for as many values as the OP can provide.
define('Auth_OpenID_AX_UNLIMITED_VALUES', 'unlimited');

// Minimum supported alias length in characters.  Here for
// completeness.
define('Auth_OpenID_AX_MINIMUM_SUPPORTED_ALIAS_LENGTH', 32);

/**
 * AX utility class.
 *
 * @package OpenID
 */
class Auth_OpenID_AX {

  /**
   * @param mixed $thing Any object which may be an
   * Auth_OpenID_AX_Error object.
   *
   * @return bool true if $thing is an Auth_OpenID_AX_Error; false
   * if not.
   */
  function isError($thing) {
    return is_a($thing, 'Auth_OpenID_AX_Error');
  }
}

/**
 * Check an alias for invalid characters; raise AXError if any are
 * found.  Return None if the alias is valid.
 */
function Auth_OpenID_AX_checkAlias($alias) {
  if (strpos($alias, ',') !== false) {
    return new Auth_OpenID_AX_Error(sprintf("Alias %s must not contain comma", $alias));
  }
  if (strpos($alias, '.') !== false) {
    return new Auth_OpenID_AX_Error(sprintf("Alias %s must not contain period", $alias));
  }
  
  return true;
}

/**
 * Results from data that does not meet the attribute exchange 1.0
 * specification
 *
 * @package OpenID
 */
class Auth_OpenID_AX_Error {

  function Auth_OpenID_AX_Error($message = null) {
    $this->message = $message;
  }
}

/**
 * Abstract class containing common code for attribute exchange
 * messages.
 *
 * @package OpenID
 */
class Auth_OpenID_AX_Message extends Auth_OpenID_Extension {
  /**
   * ns_alias: The preferred namespace alias for attribute exchange
   * messages
   */
  var $ns_alias = 'ax';
  
  /**
   * mode: The type of this attribute exchange message. This must be
   * overridden in subclasses.
   */
  var $mode = null;
  
  var $ns_uri = Auth_OpenID_AX_NS_URI;

  /**
   * Return Auth_OpenID_AX_Error if the mode in the attribute
   * exchange arguments does not match what is expected for this
   * class; true otherwise.
   *
   * @access private
   */
  function _checkMode($ax_args) {
    $mode = Auth_OpenID::arrayGet($ax_args, 'mode');
    if ($mode != $this->mode) {
      return new Auth_OpenID_AX_Error(sprintf("Expected mode '%s'; got '%s'", $this->mode, $mode));
    }
    
    return true;
  }

  /**
   * Return a set of attribute exchange arguments containing the
   * basic information that must be in every attribute exchange
   * message.
   *
   * @access private
   */
  function _newArgs() {
    return array('mode' => $this->mode);
  }
}

/**
 * Represents a single attribute in an attribute exchange
 * request. This should be added to an AXRequest object in order to
 * request the attribute.
 *
 * @package OpenID
 */
class Auth_OpenID_AX_AttrInfo {

  /**
   * Construct an attribute information object.  Do not call this
   * directly; call make(...) instead.
   *
   * @param string $type_uri The type URI for this attribute.
   *
   * @param int $count The number of values of this type to request.
   *
   * @param bool $required Whether the attribute will be marked as
   * required in the request.
   *
   * @param string $alias The name that should be given to this
   * attribute in the request.
   */
  function Auth_OpenID_AX_AttrInfo($type_uri, $count, $required, $alias) {
    /**
     * required: Whether the attribute will be marked as required
     * when presented to the subject of the attribute exchange
     * request.
     */
    $this->required = $required;
    
    /**
     * count: How many values of this type to request from the
     * subject. Defaults to one.
     */
    $this->count = $count;
    
    /**
     * type_uri: The identifier that determines what the attribute
     * represents and how it is serialized. For example, one type
     * URI representing dates could represent a Unix timestamp in
     * base 10 and another could represent a human-readable
     * string.
     */
    $this->type_uri = $type_uri;
    
    /**
     * alias: The name that should be given to this attribute in
     * the request. If it is not supplied, a generic name will be
     * assigned. For example, if you want to call a Unix timestamp
     * value 'tstamp', set its alias to that value. If two
     * attributes in the same message request to use the same
     * alias, the request will fail to be generated.
     */
    $this->alias = $alias;
  }

  /**
   * Construct an attribute information object.  For parameter
   * details, see the constructor.
   */
  function make($type_uri, $count = 1, $required = false, $alias = null) {
    if ($alias !== null) {
      $result = Auth_OpenID_AX_checkAlias($alias);
      
      if (Auth_OpenID_AX::isError($result)) {
        return $result;
      }
    }
    
    return new Auth_OpenID_AX_AttrInfo($type_uri, $count, $required, $alias);
  }

  /**
   * When processing a request for this attribute, the OP should
   * call this method to determine whether all available attribute
   * values were requested.  If self.count == UNLIMITED_VALUES, this
   * returns True.  Otherwise this returns False, in which case
   * self.count is an integer.
   */
  function wantsUnlimitedValues() {
    return $this->count === Auth_OpenID_AX_UNLIMITED_VALUES;
  }
}

/**
 * Given a namespace mapping and a string containing a comma-separated
 * list of namespace aliases, return a list of type URIs that
 * correspond to those aliases.
 *
 * @param $namespace_map The mapping from namespace URI to alias
 * @param $alias_list_s The string containing the comma-separated
 * list of aliases. May also be None for convenience.
 *
 * @return $seq The list of namespace URIs that corresponds to the
 * supplied list of aliases. If the string was zero-length or None, an
 * empty list will be returned.
 *
 * return null If an alias is present in the list of aliases but
 * is not present in the namespace map.
 */
function Auth_OpenID_AX_toTypeURIs(&$namespace_map, $alias_list_s) {
  $uris = array();
  
  if ($alias_list_s) {
    foreach (explode(',', $alias_list_s) as $alias) {
      $type_uri = $namespace_map->getNamespaceURI($alias);
      if ($type_uri === null) {
        // raise KeyError(
        // 'No type is defined for attribute name %r' % (alias,))
        return new Auth_OpenID_AX_Error(sprintf('No type is defined for attribute name %s', $alias));
      } else {
        $uris[] = $type_uri;
      }
    }
  }
  
  return $uris;
}

/**
 * An attribute exchange 'fetch_request' message. This message is sent
 * by a relying party when it wishes to obtain attributes about the
 * subject of an OpenID authentication request.
 *
 * @package OpenID
 */
class Auth_OpenID_AX_FetchRequest extends Auth_OpenID_AX_Message {
  
  var $mode = 'fetch_request';

  function Auth_OpenID_AX_FetchRequest($update_url = null) {
    /**
     * requested_attributes: The attributes that have been
     * requested thus far, indexed by the type URI.
     */
    $this->requested_attributes = array();
    
    /**
     * update_url: A URL that will accept responses for this
     * attribute exchange request, even in the absence of the user
     * who made this request.
     */
    $this->update_url = $update_url;
  }

  /**
   * Add an attribute to this attribute exchange request.
   *
   * @param attribute: The attribute that is being requested
   * @return true on success, false when the requested attribute is
   * already present in this fetch request.
   */
  function add($attribute) {
    if ($this->contains($attribute->type_uri)) {
      return new Auth_OpenID_AX_Error(sprintf("The attribute %s has already been requested", $attribute->type_uri));
    }
    
    $this->requested_attributes[$attribute->type_uri] = $attribute;
    
    return true;
  }

  /**
   * Get the serialized form of this attribute fetch request.
   *
   * @returns Auth_OpenID_AX_FetchRequest The fetch request message parameters
   */
  function getExtensionArgs() {
    $aliases = new Auth_OpenID_NamespaceMap();
    
    $required = array();
    $if_available = array();
    
    $ax_args = $this->_newArgs();
    
    foreach ($this->requested_attributes as $type_uri => $attribute) {
      if ($attribute->alias === null) {
        $alias = $aliases->add($type_uri);
      } else {
        $alias = $aliases->addAlias($type_uri, $attribute->alias);
        
        if ($alias === null) {
          return new Auth_OpenID_AX_Error(sprintf("Could not add alias %s for URI %s", $attribute->alias, $type_uri));
        }
      }
      
      if ($attribute->required) {
        $required[] = $alias;
      } else {
        $if_available[] = $alias;
      }
      
      if ($attribute->count != 1) {
        $ax_args['count.' . $alias] = strval($attribute->count);
      }
      
      $ax_args['type.' . $alias] = $type_uri;
    }
    
    if ($required) {
      $ax_args['required'] = implode(',', $required);
    }
    
    if ($if_available) {
      $ax_args['if_available'] = implode(',', $if_available);
    }
    
    return $ax_args;
  }

  /**
   * Get the type URIs for all attributes that have been marked as
   * required.
   *
   * @return A list of the type URIs for attributes that have been
   * marked as required.
   */
  function getRequiredAttrs() {
    $required = array();
    foreach ($this->requested_attributes as $type_uri => $attribute) {
      if ($attribute->required) {
        $required[] = $type_uri;
      }
    }
    
    return $required;
  }

  /**
   * Extract a FetchRequest from an OpenID message
   *
   * @param request: The OpenID request containing the attribute
   * fetch request
   *
   * @returns mixed An Auth_OpenID_AX_Error or the
   * Auth_OpenID_AX_FetchRequest extracted from the request message if
   * successful
   */
  function &fromOpenIDRequest($request) {
    $m = $request->message;
    $obj = new Auth_OpenID_AX_FetchRequest();
    $ax_args = $m->getArgs($obj->ns_uri);
    
    $result = $obj->parseExtensionArgs($ax_args);
    
    if (Auth_OpenID_AX::isError($result)) {
      return $result;
    }
    
    if ($obj->update_url) {
      // Update URL must match the openid.realm of the
      // underlying OpenID 2 message.
      $realm = $m->getArg(Auth_OpenID_OPENID_NS, 'realm', $m->getArg(Auth_OpenID_OPENID_NS, 'return_to'));
      
      if (! $realm) {
        $obj = new Auth_OpenID_AX_Error(sprintf("Cannot validate update_url %s " . "against absent realm", $obj->update_url));
      } else if (! Auth_OpenID_TrustRoot::match($realm, $obj->update_url)) {
        $obj = new Auth_OpenID_AX_Error(sprintf("Update URL %s failed validation against realm %s", $obj->update_url, $realm));
      }
    }
    
    return $obj;
  }

  /**
   * Given attribute exchange arguments, populate this FetchRequest.
   *
   * @return $result Auth_OpenID_AX_Error if the data to be parsed
   * does not follow the attribute exchange specification. At least
   * when 'if_available' or 'required' is not specified for a
   * particular attribute type.  Returns true otherwise.
   */
  function parseExtensionArgs($ax_args) {
    $result = $this->_checkMode($ax_args);
    if (Auth_OpenID_AX::isError($result)) {
      return $result;
    }
    
    $aliases = new Auth_OpenID_NamespaceMap();
    
    foreach ($ax_args as $key => $value) {
      if (strpos($key, 'type.') === 0) {
        $alias = substr($key, 5);
        $type_uri = $value;
        
        $alias = $aliases->addAlias($type_uri, $alias);
        
        if ($alias === null) {
          return new Auth_OpenID_AX_Error(sprintf("Could not add alias %s for URI %s", $alias, $type_uri));
        }
        
        $count_s = Auth_OpenID::arrayGet($ax_args, 'count.' . $alias);
        if ($count_s) {
          $count = Auth_OpenID::intval($count_s);
          if (($count === false) && ($count_s === Auth_OpenID_AX_UNLIMITED_VALUES)) {
            $count = $count_s;
          }
        } else {
          $count = 1;
        }
        
        if ($count === false) {
          return new Auth_OpenID_AX_Error(sprintf("Integer value expected for %s, got %s", 'count.' . $alias, $count_s));
        }
        
        $attrinfo = Auth_OpenID_AX_AttrInfo::make($type_uri, $count, false, $alias);
        
        if (Auth_OpenID_AX::isError($attrinfo)) {
          return $attrinfo;
        }
        
        $this->add($attrinfo);
      }
    }
    
    $required = Auth_OpenID_AX_toTypeURIs($aliases, Auth_OpenID::arrayGet($ax_args, 'required'));
    
    foreach ($required as $type_uri) {
      $attrib = & $this->requested_attributes[$type_uri];
      $attrib->required = true;
    }
    
    $if_available = Auth_OpenID_AX_toTypeURIs($aliases, Auth_OpenID::arrayGet($ax_args, 'if_available'));
    
    $all_type_uris = array_merge($required, $if_available);
    
    foreach ($aliases->iterNamespaceURIs() as $type_uri) {
      if (! in_array($type_uri, $all_type_uris)) {
        return new Auth_OpenID_AX_Error(sprintf('Type URI %s was in the request but not ' . 'present in "required" or "if_available"', $type_uri));
      
      }
    }
    
    $this->update_url = Auth_OpenID::arrayGet($ax_args, 'update_url');
    
    return true;
  }

  /**
   * Iterate over the AttrInfo objects that are contained in this
   * fetch_request.
   */
  function iterAttrs() {
    return array_values($this->requested_attributes);
  }

  function iterTypes() {
    return array_keys($this->requested_attributes);
  }

  /**
   * Is the given type URI present in this fetch_request?
   */
  function contains($type_uri) {
    return in_array($type_uri, $this->iterTypes());
  }
}

/**
 * An abstract class that implements a message that has attribute keys
 * and values. It contains the common code between fetch_response and
 * store_request.
 *
 * @package OpenID
 */
class Auth_OpenID_AX_KeyValueMessage extends Auth_OpenID_AX_Message {

  function Auth_OpenID_AX_KeyValueMessage() {
    $this->data = array();
  }

  /**
   * Add a single value for the given attribute type to the
   * message. If there are already values specified for this type,
   * this value will be sent in addition to the values already
   * specified.
   *
   * @param type_uri: The URI for the attribute
   * @param value: The value to add to the response to the relying
   * party for this attribute
   * @return null
   */
  function addValue($type_uri, $value) {
    if (! array_key_exists($type_uri, $this->data)) {
      $this->data[$type_uri] = array();
    }
    
    $values = & $this->data[$type_uri];
    $values[] = $value;
  }

  /**
   * Set the values for the given attribute type. This replaces any
   * values that have already been set for this attribute.
   *
   * @param type_uri: The URI for the attribute
   * @param values: A list of values to send for this attribute.
   */
  function setValues($type_uri, &$values) {
    $this->data[$type_uri] = & $values;
  }

  /**
   * Get the extension arguments for the key/value pairs contained
   * in this message.
   *
   * @param aliases: An alias mapping. Set to None if you don't care
   * about the aliases for this request.
   *
   * @access private
   */
  function _getExtensionKVArgs(&$aliases) {
    if ($aliases === null) {
      $aliases = new Auth_OpenID_NamespaceMap();
    }
    
    $ax_args = array();
    
    foreach ($this->data as $type_uri => $values) {
      $alias = $aliases->add($type_uri);
      
      $ax_args['type.' . $alias] = $type_uri;
      $ax_args['count.' . $alias] = strval(count($values));
      
      foreach ($values as $i => $value) {
        $key = sprintf('value.%s.%d', $alias, $i + 1);
        $ax_args[$key] = $value;
      }
    }
    
    return $ax_args;
  }

  /**
   * Parse attribute exchange key/value arguments into this object.
   *
   * @param ax_args: The attribute exchange fetch_response
   * arguments, with namespacing removed.
   *
   * @return Auth_OpenID_AX_Error or true
   */
  function parseExtensionArgs($ax_args) {
    $result = $this->_checkMode($ax_args);
    if (Auth_OpenID_AX::isError($result)) {
      return $result;
    }
    
    $aliases = new Auth_OpenID_NamespaceMap();
    
    foreach ($ax_args as $key => $value) {
      if (strpos($key, 'type.') === 0) {
        $type_uri = $value;
        $alias = substr($key, 5);
        
        $result = Auth_OpenID_AX_checkAlias($alias);
        
        if (Auth_OpenID_AX::isError($result)) {
          return $result;
        }
        
        $alias = $aliases->addAlias($type_uri, $alias);
        
        if ($alias === null) {
          return new Auth_OpenID_AX_Error(sprintf("Could not add alias %s for URI %s", $alias, $type_uri));
        }
      }
    }
    
    foreach ($aliases->iteritems() as $pair) {
      list($type_uri, $alias) = $pair;
      
      if (array_key_exists('count.' . $alias, $ax_args)) {
        
        $count_key = 'count.' . $alias;
        $count_s = $ax_args[$count_key];
        
        $count = Auth_OpenID::intval($count_s);
        
        if ($count === false) {
          return new Auth_OpenID_AX_Error(sprintf("Integer value expected for %s, got %s", 'count. %s' . $alias, $count_s, Auth_OpenID_AX_UNLIMITED_VALUES));
        }
        
        $values = array();
        for ($i = 1; $i < $count + 1; $i ++) {
          $value_key = sprintf('value.%s.%d', $alias, $i);
          
          if (! array_key_exists($value_key, $ax_args)) {
            return new Auth_OpenID_AX_Error(sprintf("No value found for key %s", $value_key));
          }
          
          $value = $ax_args[$value_key];
          $values[] = $value;
        }
      } else {
        $key = 'value.' . $alias;
        
        if (! array_key_exists($key, $ax_args)) {
          return new Auth_OpenID_AX_Error(sprintf("No value found for key %s", $key));
        }
        
        $value = $ax_args['value.' . $alias];
        
        if ($value == '') {
          $values = array();
        } else {
          $values = array($value);
        }
      }
      
      $this->data[$type_uri] = $values;
    }
    
    return true;
  }

  /**
   * Get a single value for an attribute. If no value was sent for
   * this attribute, use the supplied default. If there is more than
   * one value for this attribute, this method will fail.
   *
   * @param type_uri: The URI for the attribute
   * @param default: The value to return if the attribute was not
   * sent in the fetch_response.
   *
   * @return $value Auth_OpenID_AX_Error on failure or the value of
   * the attribute in the fetch_response message, or the default
   * supplied
   */
  function getSingle($type_uri, $default = null) {
    $values = Auth_OpenID::arrayGet($this->data, $type_uri);
    if (! $values) {
      return $default;
    } else if (count($values) == 1) {
      return $values[0];
    } else {
      return new Auth_OpenID_AX_Error(sprintf('More than one value present for %s', $type_uri));
    }
  }

  /**
   * Get the list of values for this attribute in the
   * fetch_response.
   *
   * XXX: what to do if the values are not present? default
   * parameter? this is funny because it's always supposed to return
   * a list, so the default may break that, though it's provided by
   * the user's code, so it might be okay. If no default is
   * supplied, should the return be None or []?
   *
   * @param type_uri: The URI of the attribute
   *
   * @return $values The list of values for this attribute in the
   * response. May be an empty list.  If the attribute was not sent
   * in the response, returns Auth_OpenID_AX_Error.
   */
  function get($type_uri) {
    if (array_key_exists($type_uri, $this->data)) {
      return $this->data[$type_uri];
    } else {
      return new Auth_OpenID_AX_Error(sprintf("Type URI %s not found in response", $type_uri));
    }
  }

  /**
   * Get the number of responses for a particular attribute in this
   * fetch_response message.
   *
   * @param type_uri: The URI of the attribute
   *
   * @returns int The number of values sent for this attribute.  If
   * the attribute was not sent in the response, returns
   * Auth_OpenID_AX_Error.
   */
  function count($type_uri) {
    if (array_key_exists($type_uri, $this->data)) {
      return count($this->get($type_uri));
    } else {
      return new Auth_OpenID_AX_Error(sprintf("Type URI %s not found in response", $type_uri));
    }
  }
}

/**
 * A fetch_response attribute exchange message.
 *
 * @package OpenID
 */
class Auth_OpenID_AX_FetchResponse extends Auth_OpenID_AX_KeyValueMessage {
  var $mode = 'fetch_response';

  function Auth_OpenID_AX_FetchResponse($update_url = null) {
    $this->Auth_OpenID_AX_KeyValueMessage();
    $this->update_url = $update_url;
  }

  /**
   * Serialize this object into arguments in the attribute exchange
   * namespace
   *
   * @return $args The dictionary of unqualified attribute exchange
   * arguments that represent this fetch_response, or
   * Auth_OpenID_AX_Error on error.
   */
  function getExtensionArgs($request = null) {
    $aliases = new Auth_OpenID_NamespaceMap();
    
    $zero_value_types = array();
    
    if ($request !== null) {
      // Validate the data in the context of the request (the
      // same attributes should be present in each, and the
      // counts in the response must be no more than the counts
      // in the request)
      

      foreach ($this->data as $type_uri => $unused) {
        if (! $request->contains($type_uri)) {
          return new Auth_OpenID_AX_Error(sprintf("Response attribute not present in request: %s", $type_uri));
        }
      }
      
      foreach ($request->iterAttrs() as $attr_info) {
        // Copy the aliases from the request so that reading
        // the response in light of the request is easier
        if ($attr_info->alias === null) {
          $aliases->add($attr_info->type_uri);
        } else {
          $alias = $aliases->addAlias($attr_info->type_uri, $attr_info->alias);
          
          if ($alias === null) {
            return new Auth_OpenID_AX_Error(sprintf("Could not add alias %s for URI %s", $attr_info->alias, $attr_info->type_uri));
          }
        }
        
        if (array_key_exists($attr_info->type_uri, $this->data)) {
          $values = $this->data[$attr_info->type_uri];
        } else {
          $values = array();
          $zero_value_types[] = $attr_info;
        }
        
        if (($attr_info->count != Auth_OpenID_AX_UNLIMITED_VALUES) && ($attr_info->count < count($values))) {
          return new Auth_OpenID_AX_Error(sprintf("More than the number of requested values " . "were specified for %s", $attr_info->type_uri));
        }
      }
    }
    
    $kv_args = $this->_getExtensionKVArgs($aliases);
    
    // Add the KV args into the response with the args that are
    // unique to the fetch_response
    $ax_args = $this->_newArgs();
    
    // For each requested attribute, put its type/alias and count
    // into the response even if no data were returned.
    foreach ($zero_value_types as $attr_info) {
      $alias = $aliases->getAlias($attr_info->type_uri);
      $kv_args['type.' . $alias] = $attr_info->type_uri;
      $kv_args['count.' . $alias] = '0';
    }
    
    $update_url = null;
    if ($request) {
      $update_url = $request->update_url;
    } else {
      $update_url = $this->update_url;
    }
    
    if ($update_url) {
      $ax_args['update_url'] = $update_url;
    }
    
    Auth_OpenID::update(&$ax_args, $kv_args);
    
    return $ax_args;
  }

  /**
   * @return $result Auth_OpenID_AX_Error on failure or true on
   * success.
   */
  function parseExtensionArgs($ax_args) {
    $result = parent::parseExtensionArgs($ax_args);
    
    if (Auth_OpenID_AX::isError($result)) {
      return $result;
    }
    
    $this->update_url = Auth_OpenID::arrayGet($ax_args, 'update_url');
    
    return true;
  }

  /**
   * Construct a FetchResponse object from an OpenID library
   * SuccessResponse object.
   *
   * @param success_response: A successful id_res response object
   *
   * @param signed: Whether non-signed args should be processsed. If
   * True (the default), only signed arguments will be processsed.
   *
   * @return $response A FetchResponse containing the data from the
   * OpenID message
   */
  function fromSuccessResponse($success_response, $signed = true) {
    $obj = new Auth_OpenID_AX_FetchResponse();
    if ($signed) {
      $ax_args = $success_response->getSignedNS($obj->ns_uri);
    } else {
      $ax_args = $success_response->message->getArgs($obj->ns_uri);
    }
    if ($ax_args === null || Auth_OpenID::isFailure($ax_args) || sizeof($ax_args) == 0) {
      return null;
    }
    
    $result = $obj->parseExtensionArgs($ax_args);
    if (Auth_OpenID_AX::isError($result)) {
      #XXX log me
      return null;
    }
    return $obj;
  }
}

/**
 * A store request attribute exchange message representation.
 *
 * @package OpenID
 */
class Auth_OpenID_AX_StoreRequest extends Auth_OpenID_AX_KeyValueMessage {
  var $mode = 'store_request';

  /**
   * @param array $aliases The namespace aliases to use when making
   * this store response. Leave as None to use defaults.
   */
  function getExtensionArgs($aliases = null) {
    $ax_args = $this->_newArgs();
    $kv_args = $this->_getExtensionKVArgs($aliases);
    Auth_OpenID::update(&$ax_args, $kv_args);
    return $ax_args;
  }
}

/**
 * An indication that the store request was processed along with this
 * OpenID transaction.  Use make(), NOT the constructor, to create
 * response objects.
 *
 * @package OpenID
 */
class Auth_OpenID_AX_StoreResponse extends Auth_OpenID_AX_Message {
  var $SUCCESS_MODE = 'store_response_success';
  var $FAILURE_MODE = 'store_response_failure';

  /**
   * Returns Auth_OpenID_AX_Error on error or an
   * Auth_OpenID_AX_StoreResponse object on success.
   */
  function &make($succeeded = true, $error_message = null) {
    if (($succeeded) && ($error_message !== null)) {
      return new Auth_OpenID_AX_Error('An error message may only be ' . 'included in a failing fetch response');
    }
    
    return new Auth_OpenID_AX_StoreResponse($succeeded, $error_message);
  }

  function Auth_OpenID_AX_StoreResponse($succeeded = true, $error_message = null) {
    if ($succeeded) {
      $this->mode = $this->SUCCESS_MODE;
    } else {
      $this->mode = $this->FAILURE_MODE;
    }
    
    $this->error_message = $error_message;
  }

  /**
   * Was this response a success response?
   */
  function succeeded() {
    return $this->mode == $this->SUCCESS_MODE;
  }

  function getExtensionArgs() {
    $ax_args = $this->_newArgs();
    if ((! $this->succeeded()) && $this->error_message) {
      $ax_args['error'] = $this->error_message;
    }
    
    return $ax_args;
  }
}
